package controllers.data.providers;

import java.io.IOException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import models.EntityContainer;
import models.PropertyModel;
import models.Target;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.ObjectMapper;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;

import controllers.utils.EntityUtil;


public class Mql
{
    // TODO: At the end of development these can be made final
    public static String                    FREEBASE_QUERY    = "mqlread?query=";
    public static String                    FREEBASE_SEARCH   = "search?query=";
    public static String                    FREEBASE_TOPIC    = "topic/";
    public static String                    FREEBASE_LIMIT    = "?limit=0";
    public static String                    FREEBASE_API_KEY  = "&key=AIzaSyBUWf26rACddG1Nwuu3B0nP4ww_QWWJ2A8";
    private static HttpClient               httpclient        = getThreadSafeClient();

    private static HashMap<String, Integer> PREFERRABLE_NAMES = new HashMap<String, Integer>() {
                                                                  private static final long serialVersionUID = 1L;

                                                                  {
                                                                      put("/type/object/name", Integer.MAX_VALUE);
                                                                      put("/symbols/name_source/namesakes", 80);
                                                                      put("/type/object/id", 60);
                                                                      put("/common/topic/alias", 40);
                                                                      put("/type/object/key", 20);
                                                                  }
                                                              };


    public static EntityContainer loadInstance(String instance, Target target)
    {
        // Remove excess slash if present
        instance = instance.startsWith("/") ? instance.substring(1) : instance;

        String url = target.url() + FREEBASE_TOPIC + instance + FREEBASE_LIMIT + FREEBASE_API_KEY;

        // Create PropertyModel
        JsonFactory jf = new JsonFactory();
        jf.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
        JsonParser jp = null;
        try {
            HttpResponse response = httpclient.execute(new HttpGet(url));
            if(response == null)
                return null;
            HttpEntity entity = response.getEntity();
            jp = jf.createJsonParser(EntityUtils.toString(entity));
            entity.consumeContent();
        } catch(UnknownHostException uhe) {
            System.err.println("Please check your internet connection.");
        } catch(IOException e) {
            e.printStackTrace();
        }

        EntityContainer result = null;
        Model model = ModelFactory.createDefaultModel();

        ObjectMapper mapper = new ObjectMapper();

        String id = null;
        JsonNode node = null;
        try {
            node = mapper.readTree(jp);
            if(node.has("error")) {
                try {
                    System.out.println("Querying too fast. Waiting for 3 seconds and retrying");
                    Thread.sleep(3000);
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Continuing");
                return loadInstance(instance, target);
            }
            id = getId(node);
        } catch(IOException e) {
            e.printStackTrace();
        }

        Parsing: {
            if(node == null || id == null) {
                System.err.println("Something that should not be possible happened...");
                break Parsing;
            }

            try {
                // This is a bit of a hack, but I had absolutely no intention of rewriting the parsing section.
                jp = jf.createJsonParser(node.toString());
                if(jp.nextToken() != JsonToken.START_OBJECT) {
                    System.err.println("Something that should not be possible happened...");
                    return null;
                }
                jp.nextToken();
                jp.nextToken();

                Resource root = model.createResource(id);
                result = new EntityContainer(root, target);

                String fieldname = null;
                PropertyModel pm = null;
                Map<String, String> temp = null;
                while(jp.nextToken() != null) {
                    switch(jp.getCurrentToken()) {
                        case FIELD_NAME:
                            fieldname = jp.getCurrentName();
                            if(fieldname.startsWith("/")) {
                                Property property = model.createProperty(fieldname);
                                pm = new PropertyModel(property);
                            }
                        break;
                        case START_OBJECT:
                            temp = new HashMap<String, String>();
                        break;
                        case START_ARRAY:
                        break;
                        case VALUE_STRING:
                            temp.put(fieldname, jp.getText());
                        break;
                        case END_ARRAY:
                        break;
                        case END_OBJECT:
                            String language = temp.get("lang") != null && !temp.get("lang").isEmpty() ? temp.get("lang") : "XX";
                            pm.addValue(language, temp.get("text"));
                            if(temp.containsKey("id") && temp.get("id").startsWith("/")) {
                                pm.linkedInstances.add(temp.get("id"));
                            }
                            // When a new property is found, the previous one is finished
                            if(pm != null) {
                                result.addProperty(pm);
                            }
                        break;
                        default:
                        break;
                    }
                }
                jp.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

        // set readable name
        setReadableName(result);
        return result;
    }


    private static void setReadableName(EntityContainer instance)
    {
        int currentPreference = 0;
        Set<String> names = new HashSet<>();
        ReadableName: for(PropertyModel pm : instance.getProperties()) {
            for(String s : PREFERRABLE_NAMES.keySet())
                if(pm.property.getURI().equals(s) && currentPreference < PREFERRABLE_NAMES.get(s)) {
                    currentPreference = PREFERRABLE_NAMES.get(s);
                    names.clear();
                    Set<String> temp = pm.getValues().get("en");
                    if(temp != null) {
                        names.addAll(EntityUtil.getLocalNameSet(temp));
                    }
                    temp = pm.getValues().get("XX");
                    if(temp != null) {
                        names.addAll(EntityUtil.getLocalNameSet(temp));
                    }
                }
            if(currentPreference == Integer.MAX_VALUE) {
                break ReadableName;
            }
        }

        instance.setReadableNames(names.toArray(new String[names.size()]));
    }


    /**
     * Send a mql request to a destination - e.g. FREEBASE
     * 
     * @param request
     *            mql query
     * @param destination
     *            e.g. FREEBASE
     * @return
     */
    public static Set<EntityContainer> query(String request, String destination)
    {
        JsonFactory jf = new JsonFactory();
        jf.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
        ObjectMapper mapper = new ObjectMapper();
        JsonParser jp = null;
        try {
            String url = destination + URLEncoder.encode(request, "UTF-8") + FREEBASE_API_KEY;
            HttpResponse response = httpclient.execute(new HttpGet(url));
            if(response == null)
                return null;
            HttpEntity entity = response.getEntity();
            jp = jf.createJsonParser(EntityUtils.toString(entity));
            entity.consumeContent();
        } catch(IOException e) {
            e.printStackTrace();
        }

        Set<EntityContainer> result = new HashSet<>();

        JsonNode rootNode = null;
        try {
            rootNode = mapper.readTree(jp);
        } catch(IOException e) {
            e.printStackTrace();
        }

        if(destination.contains(FREEBASE_SEARCH))
            result = search(rootNode);
        else if(destination.contains(FREEBASE_QUERY))
            System.err.println("NYI");
        return result;
    }


    /**
     * 
     * @param rootNode
     * @return
     */
    private static Set<EntityContainer> search(JsonNode rootNode)
    {
        Set<EntityContainer> result = new HashSet<>();

        Model model = ModelFactory.createDefaultModel();
        Parsing: {
            if(rootNode == null)
                break Parsing;
            Iterator<JsonNode> searchResults = rootNode.path("result").getElements();
            while(searchResults.hasNext()) {
                JsonNode node = searchResults.next();

                String id = getId(node);
                String score = node.get("score").asText();
                Resource res = model.createResource(id);
                EntityContainer ec = new EntityContainer(res, Target.FREEBASE);
                ec.setConfidence(Double.valueOf(score));

                // if(ec.getURI().equals("/en/plato"))
                // System.out.println("DEBUG77: " + ec.getURI() + " " + ec.getLocalName() + " " + ec.getConfidence());

                result.add(ec);
            }
        }

        return result;
    }


    /**
     * 
     * @param node
     * @return
     */
    private static String getId(JsonNode node)
    {
        if(node.has("property")) {
            if(!node.findPath("/type/object/id").isMissingNode()) {
                node.get("/type/object/id");
                JsonNode propertyNode = node.findPath("/type/object/id").get("values").get(0);
                String value = propertyNode.path("value").asText();
                if(!value.isEmpty()) { return value; }
                value = propertyNode.path("text").asText();
                if(!value.isEmpty()) { return value; }
            }
            // if(!node.findPath("/type/object/key").isMissingNode()) {
            // JsonNode propertyNode = node.findPath("/type/object/key").get("values").get(0);
            // String value = propertyNode.path("value").asText();
            // if(!value.isEmpty()) { return value; }
            // value = propertyNode.path("text").asText();
            // if(!value.isEmpty()) { return value; }
            // }
        }
        if(node.has("id")) { return node.get("id").asText(); }
        // else if(node.has("name") && node.get("name").asText().length() > 0) { return "/en/" + node.get("name").asText().replace(" ", "_").toLowerCase(); }

        // This happens for search results.
        return node.path("mid").asText();
    }


    /**
     * Provides a threadsafe http client so that we adhere to the contract of the {@link HttpClient}.
     * 
     * @return
     */
    public static HttpClient getThreadSafeClient()
    {
        HttpClient client = new DefaultHttpClient();
        ClientConnectionManager mgr = client.getConnectionManager();
        HttpParams params = client.getParams();
        client = new DefaultHttpClient(new ThreadSafeClientConnManager(params,

        mgr.getSchemeRegistry()), params);
        return client;
    }
}
